unit MUDAnsi;
//Version 11.0

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, menus, Clipbrd, TnCnx;
  {$M+}

type
  strarray = array[0..25] of string;
  TMUDAnsi = class;
  TMUDAnsiSB = class;
  TTextAttrib = word;
  TLinkIndex = byte;
  TTextStruct = record
    Textattrib:ttextattrib;
    linkindex:TLinkIndex;
  end;
  TTextArray = array[0..65534] of ttextstruct;
  PTTextArray = ^TTextArray;
  TAttrib = array[0..120] of TTextstruct;
  PTAttrib = ^TAttrib;
  TMUDCodeEvent = procedure(DATA: string) of object;
  TMUDFilterEvent = procedure(DATA: string) of object;
  TMUDLineCompletedEvent = procedure(str: string; ob: TObject) of object;
  TMUDCopyToATEvent = procedure(DATA: string) of object;
  TMUDTriggerEvent = procedure(index: integer; variables: strarray) of object;
  TMUDLinkEvent = procedure(target: string) of object;

  TMUDAnsi = class(TCustomControl)
  protected
    qJS, ScanState, JSState, incstring, FIncStr, fCodes, LastAnsi, texttoinsert: string;
    fHyperLinks: array[1..255] of string;
    ftabsize, fInc, fermargin, qMaxLines: integer;
    SelStartX, SelStartY, SelEndX, SelEndY, lastselx, lastsely, processing: integer;
    bgcreated, flashon, fShowBadCodes, BusySelecting, justselecting: boolean;
    inserting, filtering, ffullflash, fCopyToScrollback: boolean;
    fOnOptions: TNotifyEvent;
    fonlinkclick: tMUDlinkevent;
    ANSI_DFG, ANSI_FG, ANSI_DBG, ANSI_BG: word;
    texttoinsertattribs: PTTextArray;
    CurrentAttributes, DefaultAttributes: TTextAttrib;
    bdc: hdc;
    bbmp: hbitmap;
    currentlink: byte;
    flinkfg, flinkbg: tcolor;
    { Private declarations }
    comptype: string;
    OldWidth, OldHeight, maxstrlen: integer;
    fEnableJumpstart, fenablelinks, fAutoCopy, feventsonfilter, filterline: boolean;
    FColor, fselFGcolor, fselbgcolor, FTextColor: TColor;
    fscrollback:TMUDAnsiSB;
    FOnBeep, fOnJumpstart: TNotifyEvent;
    FOnCodeReceived: TMUDCodeEvent;
    FOnCopyToAutotyper: TMUDCopyToATEvent;
    fOnMouseMove2: TMouseMoveEvent;
    FOnTrigger: TMUDTriggerEvent;
    FOnFilter: TMUDFilterEvent;
    fOnLineCompleted: TMUDLineCompletedEvent;
    MyPopupMenu: TPopupMenu;
    MyMenuItems: array[0..3] of TMenuItem;
    function GetLink(idx: integer): string;
    procedure SetEnableLinks(ena: boolean);
    procedure SetLinkFG(col: tcolor);
    procedure SetLinkBG(col: tcolor);
    procedure SetColors(i, val: integer);
    function GetColors(i: integer): integer;
    procedure SetInc(value: integer);
    procedure SetOnCopyToAutotyper(ate: TMUDCopyToATEvent);
    procedure SetAutocopy(b: boolean);
    procedure processtriggers(str: string; ptr: PTAttrib);
    procedure processurls(str: string; ptr: PTAttrib);
    function processfilters(str: string): boolean;
    procedure Showline(adc: hdc; lineno, location, offset, finish: integer);
    procedure Paint; override;
    procedure SetMaxLines(value: integer);
    procedure SetLines(Value: TStrings);
    procedure SetFont(Fnt: TFont);
    procedure SetColor(Col: Tcolor);
    procedure setselfgcolor(col: tcolor);
    procedure setselbgcolor(col: tcolor);
    procedure fSetTextColor(Col: Tcolor);
    procedure DrawBack(DC: HDC; aRect: TRect);
    procedure WMSize(var M: TWMSize); message wm_Size;
    procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
    procedure WMGetDlgCode(var M: TWMGetDlgCode); message wm_GetDlgCode;
    procedure FontChanged(Sender: TObject);
    procedure UponMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure UponMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure UpOnMouseMove(Sender: TObject;
      Shift: TShiftState; X, Y: Integer);
    procedure SetSB(aScroll: TMUDAnsiSB);
    function GetCurrentAttribs: TTextAttrib;
    function IsVis(lineno: integer): boolean;
    procedure CallOnOptions(Sender: TObject);
    procedure UponDeactivate(Sender: TObject);
  public
    { Public declarations }
    CurScrollPos: integer;
    colorArray: array[0..15] of integer;
    FCharSize: TPoint;
    FClientSize: TPoint;
    FLines: TStrings;
    Triggers: array [0..259] of string;
    TriggerColor, TriggerBGColor: array [0..259] of word;
    TriggerTerminal, TriggerCaseInsensitive, TriggerFilter, TriggerRetain: array[0..259] of boolean;
    Filters: array[0..259] of string;
    FiltersCaseInsensitive, FiltersRetain: array[0..259] of boolean;
    FFont: TFont;
    fMySocket: TTnCnx;
    property ScrollBack: TMUDAnsiSB read fScrollback write SetSB;
    constructor Create(AnOwner: TComponent); override;
    destructor Destroy; override;
    procedure clear;
    procedure unselect;
    procedure Scrollup(sv: integer);
    procedure Scrolldown(sv: integer);
    procedure Scrollup2(sv: integer);
    procedure Scrolldown2(sv: integer);
    procedure Scrollto(sv: integer);
    procedure add(const Str: string);
    procedure RecalcRange;
    procedure InsertFormatedText(FG, BG: integer; InsertedText: string; iital, iuline: boolean);
    procedure InsertLink(Text, target: string);
    procedure Ansify(var S: string; P: PTTextArray);
    procedure Process(str: string);
    procedure ProcessInserted(var j: integer);
    procedure CopySelectedText(Sender: TObject);
    procedure CopyToAutotyper(Sender: TObject);
    function SelectedText: string;
    function SelectedRect: TRect;
    function IsSelected(x, y: integer): boolean;
    procedure cleartriggers;
    procedure clearfilters;
    property TextColors[i: integer]: integer read getcolors write setcolors;
    property HyperLinks[idx: integer]: string read getlink;
    procedure flash;
    function findnext(str: string): integer;
    function find(str: string): integer;
  published
    { Published declarations }
    property EnableLinks: boolean read fenablelinks write setenablelinks;
    property JumpstartString: string read qJS write qJS;
    property PopupMenu;
    property Linewrap: integer read maxstrlen write maxstrlen;
    property EnableJumpstart: boolean read fenablejumpstart write fenablejumpstart;
    property OnJumpstart: TNotifyEvent read fonjumpstart write fonjumpstart;
    property TabSize: integer read ftabsize write ftabsize;
    property ShowBadCodes: boolean read fshowbadcodes write fshowbadcodes;
    property Increment: integer read finc write setinc;
    property ErrorMargin: integer read fermargin write fermargin;
    property OnOptions: TNotifyEvent read fonoptions write fonoptions;
    property CopyToScrollBack: boolean read fcopytoscrollback write fcopytoscrollback;
    property AutoCopy: boolean read fautocopy write setautocopy;
    property MySocket: TTnCnx read fMySocket write fMySocket;
    property MaxLines: integer read qMaxLines write SetMaxLines;
    property Font: TFont read FFont write SetFont;
    property Color: TColor read FColor write SetColor;
    property SelFGColor: TColor read FSelFGColor write setselfgcolor;
    property SelBGColor: TColor read FSelBGColor write setselbgcolor;
    property LinkFG: TColor read flinkfg write setlinkfg;
    property LinkBG: TColor read flinkbg write setlinkbg;
    property Lines: TStrings read FLines write SetLines;
    property TextColor: TColor read FTextColor write fSetTextColor;
    property EventsOnFilter: boolean read feventsonfilter write feventsonfilter;
    property FullFlash: boolean read ffullflash write ffullflash;
    property Hint;
    property ShowHint;
    property Align;
    property HelpContext;
    property TabStop;
    property OnDblClick;
    property OnClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnStartDrag;
    property OnCodeReceived: TMUDCodeEvent read FOnCodeReceived write FOnCodeReceived;
    property OnBeep: TNotifyEvent read fOnBeep write FOnBeep;
    property OnTrigger: TMUDTriggerEvent read FOnTrigger write FOnTrigger;
    property OnCopyToAutotyper: TMUDCopyToATEvent read FOnCopyToAutoTyper
      write SetOnCopyToAutotyper;
    property OnMouseMove2: TMouseMoveEvent read fOnMouseMove2 write fOnMouseMove2;
    property OnLineCompleted: TMUDLineCompletedEvent read fOnLineCompleted
      write fOnLineCompleted;
    property OnFilter: TMUDFilterEvent read fOnFilter write fOnFilter;
    property OnLinkClick: TMUDLinkEvent read fonlinkclick write fonlinkclick;
end;

  TMUDAnsiSB = class(TMUDAnsi)
  protected
    fDisplay: TMUDAnsi;
    procedure setDisplay(aDisplay: TMUDAnsi);
  public
    constructor Create(AnOwner: TComponent); override;
    property Display: TMUDAnsi read fdisplay write setDisplay;
    procedure AddLine(str: string; ptr: PTAttrib);
  published
end;

CONST
  defcolors: array[0..15] of integer = (
    clBlack, clMaroon, clGreen, clOlive, clNavy, clPurple, clTeal, clSilver,
    clGray, clRed, clLime, clYellow, clBlue, clFuchsia, clAqua, clWhite);

  ANSI_RESET:      string = chr(27)+'[0m';
  ANSI_HILITE:     string = chr(27)+'[1m';
  ANSI_ITALICS:    string = chr(27)+'[3m';
  ANSI_UNDERLINE:  string = chr(27)+'[4m';
  ANSI_FLASH:      string = chr(27)+'[5m';
  ANSI_INVERSE:    string = chr(27)+'[7m';

  ANSI_BLACK:      string = chr(27)+'[30m';
  ANSI_RED:        string = chr(27)+'[31m';
  ANSI_GREEN:      string = chr(27)+'[32m';
  ANSI_BROWN:      string = chr(27)+'[33m';
  ANSI_BLUE:       string = chr(27)+'[34m';
  ANSI_MAGENTA:    string = chr(27)+'[35m';
  ANSI_CYAN:       string = chr(27)+'[36m';
  ANSI_GREY:       string = chr(27)+'[37m';

  ANSI_HIBLACK:    string = chr(27)+'[30;1m';
  ANSI_HIRED:      string = chr(27)+'[31;1m';
  ANSI_HIGREEN:    string = chr(27)+'[32;1m';
  ANSI_YELLOW:     string = chr(27)+'[33;1m';
  ANSI_HIBLUE:     string = chr(27)+'[34;1m';
  ANSI_HIMAGENTA:  string = chr(27)+'[35;1m';
  ANSI_HICYAN:     string = chr(27)+'[36;1m';
  ANSI_WHITE:      string = chr(27)+'[37;1m';

  ANSI_BLACK_BG:   string = chr(27)+'[40m';
  ANSI_RED_BG:     string = chr(27)+'[41m';
  ANSI_GREEN_BG:   string = chr(27)+'[42m';
  ANSI_BROWN_BG:   string = chr(27)+'[43m';
  ANSI_BLUE_BG:    string = chr(27)+'[44m';
  ANSI_MAGENTA_BG: string = chr(27)+'[45m';
  ANSI_CYAN_BG:    string = chr(27)+'[46m';
  ANSI_GREY_BG:    string = chr(27)+'[47m';

  tsDefault:   word = $0001;
  tsFlash:     word = $0002;
  tsSelected:  word = $0004;
  tsInvis:     word = $0008;

  tsItalic:    word = $0010;
  tsUline:     word = $0020;
  tsInverse:   word = $0040;
  tsDefaultbg: word = $0080;

  tsBlack:     word = $0000;
  tsRed:       word = $0100;
  tsGreen:     word = $0200;
  tsBrown:     word = $0300;
  tsBlue:      word = $0400;
  tsMagenta:   word = $0500;
  tsCyan:      word = $0600;
  tsGrey:      word = $0700;
  tsBold:      word = $0800;

  tsBlackBG:   word = $0000;
  tsRedBG:     word = $1000;
  tsGreenBG:   word = $2000;
  tsBrownBG:   word = $3000;
  tsBlueBG:    word = $4000;
  tsMagentaBG: word = $5000;
  tsCyanBG:    word = $6000;
  tsGreyBG:    word = $7000;
  tsBoldBG:    word = $8000;

  CD1:    string = chr(1);
  CD12:   string = chr(1)+chr(2);
  CD123:  string = chr(1)+chr(2)+chr(3);
  CD1234: string = chr(1)+chr(2)+chr(3)+chr(4);

  urlstrings:array[1..7] of string = (
    'mailto:',
    'http://',
    'ftp://',
    'telnet://',
    'https://',
    'www.',
    'ftp.');
  nURLs:integer=7;

  MAXTRIGGERS: integer = 260;
  MAXFILTERS: integer = 260;

procedure Register;

implementation

function Max(i1, i2: longint): longint;
begin
  if i1 > i2 then result:=i1
  else result:=i2;
end;

function Min(i1, i2: longint): longint;
begin
  if i1 < i2 then result:=i1
  else result:=i2;
end;

Constructor TMUDAnsi.Create(AnOwner: TComponent);
var i:integer;
begin
  comptype:='display';
  for i:=0 to 15 do
  ColorArray[i]:=defcolors[i];
  inherited Create(AnOwner);
  try FLines:=TStringList.Create;
    except MessageBox(0,'Can`t create List for text lines','Error',Mb_OK);
    exit;
  end;
  fenablelinks:=true;
  ftabsize:=8;
  fShowBadCodes:=false;
  fermargin:=3;
  fenablejumpstart:=true;
  fScrollBack:=nil;
  filterline:=false;
  CurScrollPos:=0;
  OnMouseDown:=UponMouseDown;
  OnMouseUp:=UponMouseUp;
  OnMouseMove:=UponMouseMove;
  MyPopupMenu:=TPopupMenu.Create(self);
  MyMenuItems[0]:=TMenuItem.Create(self);
  MyMenuItems[0].Caption:='&Copy';
  MyMenuItems[0].OnClick:=CopySelectedText;
  MyPopupMenu.Items.Add(MyMenuItems[0]);
  MyPopupMenu.Items[0].Enabled:=false;
  MyMenuItems[1]:=TMenuItem.Create(self);
  MyMenuItems[1].Caption:='Copy to &Autotyper';
  MyMenuItems[1].OnClick:=CopyToAutotyper;
  MyPopupMenu.Items.Add(MyMenuItems[1]);
  MyPopupMenu.Items[1].Enabled:=false;
  MyMenuItems[2]:=NewLine;
  MyPopupMenu.Items.Add(MyMenuItems[2]);
  MyMenuItems[3]:=TMenuItem.Create(self);
  MyMenuItems[3].Caption:='Program &Options';
  MyMenuItems[3].OnClick:=CallOnOptions;
  MyPopupMenu.Items.Add(MyMenuItems[3]);
  MyPopupMenu.AutoPopup:=false;
  PopUpMenu:=MyPopupMenu;
  SelStartX:=-1;
  SelStartY:=-1;
  SelEndX:=-1;
  SelEndY:=-1;
  lastselx:=-1;
  lastsely:=-1;
  inserting:=false;
  BusySelecting:=false;
  justselecting:=false;
  ScanState:='NORMAL';
  JSState:='NORMAL';
  qJS:='.  Welcome to ';
  incstring:='';
  Color := ClBlack;
  ANSI_DFG:= 7;
  ANSI_FG:=7;
  ANSI_DBG:=0;
  ANSI_BG:=0;
  FFont:=TFont.Create;
  FFont.Name:='Terminal';
  TextColor:=ClSilver;
  FFont.Color:=FTextColor;
  Width:=200;
  Height:=300;
  FFont.OnChange:=FontChanged;
  canvas.Font:=ffont;
  FontChanged(nil);
  Align:=alNone;
  MaxLines:=10000;
  CurrentAttributes:=DefaultAttributes;
  increment:=0;
  fcodes:='#K%';
  getmem(texttoinsertattribs,1000*sizeof(TTextstruct));
  parent:=TWinControl(anowner);
  fselfgcolor:=clBlack;
  fselbgcolor:=clSilver;
  flinkfg:=cllime;
  flinkbg:=clblack;
  currentlink:=1;
  Repaint;
end;

destructor TMUDAnsi.Destroy;
begin
  freemem(texttoinsertattribs,1000*sizeof(TTextstruct));
  deletedc(bdc);
  deleteobject(bbmp);
  while flines.count <> 0 do begin
    dispose(PTAttrib(flines.objects[0]));
    flines.delete(0);
  end;
  FLines.Free;
  FFont.Free;
  inherited Destroy;
end;

procedure TMUDAnsi.SetLines(Value: TStrings);
begin
  FLines.Assign(Value);
  while flines.count > maxlines do begin
    dispose(PTAttrib(flines.objects[0]));
    flines.delete(0);
  end;
  Repaint;
end;


procedure TMUDAnsi.Paint;
var i, tp, bot, lt, rt: integer;
begin
  inherited Paint;
  if not bgcreated then begin
    bdc:=createcompatibledc(canvas.handle);
    bbmp:=createcompatiblebitmap(canvas.handle,width,height);
    selectobject(bdc,bbmp);
    bgcreated:=true;
  end;
  drawback(bdc,canvas.cliprect);
  bot:=min(min(fclientsize.y-1,flines.count-1),canvas.cliprect.bottom div fcharsize.y);
  tp:=canvas.cliprect.top div fcharsize.y;
  lt:=canvas.cliprect.Left div fcharsize.x + 1;
  rt:=canvas.cliprect.right div fcharsize.x+2;
  for i:=tp to bot do begin
    if (i+curScrollPos) > (flines.count-1) then exit;
    showline(bdc,i+curScrollPos,i,lt,rt);
  end;
  bitblt(canvas.handle,canvas.cliprect.left,canvas.cliprect.top,
    canvas.cliprect.right-canvas.cliprect.left,
    canvas.cliprect.bottom-canvas.cliprect.top,bdc,
    canvas.cliprect.left,canvas.cliprect.top,srccopy);
end;

procedure TMUDAnsi.DrawBack(DC: HDC; aRect: TRect);
var tempbr: tbrush;
begin
  tempbr:=tbrush.create;
  tempbr.style:=bsSolid;
  tempbr.color:=fcolor;
  fillrect(dc,rect(arect.left,arect.top,arect.right+1,arect.bottom+1),tempbr.handle);
  tempbr.destroy;
end;

procedure TMUDAnsi.WMEraseBkgnd(var Message: TWMEraseBkgnd);
var r1: TRect;
begin
  if (csDesigning in ComponentState) then exit;
  Message.Result:=1;
  if (OldWidth < ClientWidth) or (OldHeight < ClientHeight) then begin
    GetClipBox(Message.DC, r1);
    DrawBack(Message.DC, r1);
  end;
  OldWidth:=ClientWidth;
  OldHeight:=ClientHeight;
end;

procedure TMUDAnsi.SetFont(Fnt: TFont);
begin
  FFont.Assign(Fnt);
  FFont.OnChange:=FontChanged;
end;

procedure TMUDAnsi.FontChanged(Sender: TObject);
var
  DC: HDC;
  Save: THandle;
  Metrics: TTextMetric;
begin
  DC:=GetDC(0);
  Save:=SelectObject(DC,Font.Handle);
  GetTextMetrics(DC,Metrics);
  SelectObject(DC,Save);
  ReleaseDC(0,DC);
  with Metrics do begin
    FCharSize.X:=tmAveCharWidth;
    FCharSize.Y:=tmHeight+tmExternalLeading+1;
    RecalcRange;
    curscrollpos:=max(0,flines.count-fclientsize.y);
  end;
  invalidate;
end;

procedure TMUDAnsi.SetColor(Col: Tcolor);
var TempBG: TTextAttrib;
begin
  if Col <> FColor then begin
    case col of
      clBlack:   ANSI_DBG:=0;
      clMaroon:  ANSI_DBG:=1;
      clGreen:   ANSI_DBG:=2;
      clOlive:   ANSI_DBG:=3;
      clNavy:    ANSI_DBG:=4;
      clPurple:  ANSI_DBG:=5;
      clTeal:    ANSI_DBG:=6;
      clSilver:  ANSI_DBG:=7;
      clGray:    ANSI_DBG:=8;
      clRed:     ANSI_DBG:=9;
      clLime:    ANSI_DBG:=10;
      clYellow:  ANSI_DBG:=11;
      clBlue:    ANSI_DBG:=12;
      clFuchsia: ANSI_DBG:=13;
      clAqua:    ANSI_DBG:=14;
      clWhite:   ANSI_DBG:=15;
    else         ANSI_DBG:=7;
    end;
    ansi_Bg:=ansi_dBg;
    FColor:=Col;
  end;
  TempBG:=ansi_dbg shl 12;
  if CurrentAttributes=DefaultAttributes then begin
    defaultAttributes:=(defaultAttributes and $0FFF) or TempBG or tsDefault or tsdefaultbg;
    CurrentAttributes:=DefaultAttributes;
  end
  else begin
    defaultAttributes:=(defaultAttributes and $0FFF) or TempBG or tsDefault or tsdefaultbg;
  end;
  repaint;
end;

procedure TMUDAnsi.fSetTextColor(Col: Tcolor);
var TempFG: TTextAttrib;
begin
  if Col <> FTextColor then begin
    case col of
      clBlack:   ANSI_DFG:=0;
      clMaroon:  ANSI_DFG:=1;
      clGreen:   ANSI_DFG:=2;
      clOlive:   ANSI_DFG:=3;
      clNavy:    ANSI_DFG:=4;
      clPurple:  ANSI_DFG:=5;
      clTeal:    ANSI_DFG:=6;
      clSilver:  ANSI_DFG:=7;
      clGray:    ANSI_DFG:=8;
      clRed:     ANSI_DFG:=9;
      clLime:    ANSI_DFG:=10;
      clYellow:  ANSI_DFG:=11;
      clBlue:    ANSI_DFG:=12;
      clFuchsia: ANSI_DFG:=13;
      clAqua:    ANSI_DFG:=14;
      clWhite:   ANSI_DFG:=15;
    else         ANSI_DFG:=7;
    end;
    ansi_fg:=ansi_dfg;
    Ftextcolor:=col;
    ffont.color:=col;
  end;
  tempfg:=ansi_fg shl 8;
  defaultAttributes:=(defaultAttributes and $F0FF) or tempfg or tsDefault or tsdefaultbg;
  repaint;
end;

procedure TMUDAnsi.WMGetDlgCode(var M: TWMGetDlgCode);
begin
  M.Result:=dlgc_WantArrows or dlgc_WantChars;
end;

procedure TMUDAnsi.RecalcRange;
begin
  if HandleAllocated then
  begin
    FClientSize.X:=ClientWidth div FCharSize.X;
    FClientSize.Y:=ClientHeight div FCharSize.Y;
  end;
end;

procedure TMUDAnsi.add(const Str: string);
var tempattrib: PTAttrib;
begin
  try
    if Flines.Count >= MAXLINES then begin
      tempattrib:=ptattrib(flines.objects[0]);
      Flines.Delete(0);
      curscrollpos:=max(0,curscrollpos-1);
      if selstarty <> -1 then begin
        dec(selstarty);
        if selstarty < 0 then selstarty:=0;
      end;
      if selendy <> -1 then begin
        dec(selendy);
        if selendy < 0 then selendy:=0;
      end;
      if lastsely <> -1 then begin
        dec(lastsely);
        if lastsely < 0 then lastsely:=0;
      end;
      if curscrollpos=0 then repaint;
    end
    else new(tempattrib);
    if flines.count = 0 then tempattrib^[0].textattrib:=DefaultAttributes
    else tempattrib^[0].textattrib:=CurrentAttributes;
    FLines.AddObject(str,TObject(tempattrib));
    except MessageBox(0,'Can`t add line in document !','Error',Mb_OK);
  end;
end;

procedure TMUDAnsi.SetMaxLines(value:integer);
begin
  if (value < 1) or (value >100000) then exit;
  if value >= maxlines then qMaxLines:=value
  else begin
    while flines.count > value do begin
      dispose(PTAttrib(flines.objects[0]));
      flines.delete(0);
    end;
    qMaxLines:=Value;
    recalcrange;
    repaint;
  end;
end;

procedure Register;
begin
  RegisterComponents('3k Client',[TMUDAnsi,TMUDAnsiSB]);
end;

procedure TMUDAnsi.UponMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var temprect, selectedrect2: trect;
begin
  if (Sender<>self) or (busyselecting) then exit;
  if Button=mbLeft then begin
    if flines.count = 0 then exit;
    if assigned(fmysocket) then fMySocket.pause;
    selectedrect2:=selectedrect;
    If selectedrect2.top<>-1 then begin
      if ((selectedrect2.top >= curscrollpos) and
          (selectedrect2.top <= curscrollpos+fclientsize.y-1)) or
         ((selectedrect2.bottom >= curscrollpos) and
          (selectedrect2.bottom <= curscrollpos+fclientsize.y-1)) or
         ((selectedrect2.bottom >= curscrollpos) and
          (selectedrect2.top <= curscrollpos+fclientsize.y-1)) then begin
        temprect.top:=max(0,selectedrect2.top-curscrollpos)*fcharsize.y;
        temprect.bottom:=(min(selectedrect2.bottom-curscrollpos,fclientsize.y-1)+1)*fcharsize.y;
        temprect.left:=0;
        temprect.right:=width;
        SelEndY:=-1;
        SelEndX:=-1;
        SelStartY:=-1;
        SelStartX:=-1;
        invalidaterect(handle,@temprect,false);
        update;
      end;
    end;
    getwindowrect(handle,temprect);
    temprect.bottom:=temprect.bottom-1;
    SetCaptureControl(self);
    ClipCursor(@temprect);
    SelEndY:=-1;
    SelEndX:=-1;
    Selstarty:=min(curscrollpos+(y div fcharsize.y),flines.count-1);
    SelStartX:=min(x div Fcharsize.x + 1,length(flines[selstarty]));
    lastselx:=selstartx;
    lastsely:=selstarty;
    BusySelecting:=true;
    justselecting:=true;
    PopupMenu.Items[0].Enabled:=false;
    PopupMenu.Items[1].Enabled:=false;
    TForm(Owner).OnDeactivate:=UponDeactivate;
    Application.OnDeactivate:=UponDeactivate;
  end
  else if Button=mbRight then PopupMenu.Popup(clientorigin.x+x,clientorigin.y+y);
end;

procedure TMUDAnsi.UponMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var temprect: trect;
begin
  if (not BusySelecting) or (Sender <> Self) then exit;
  if Button=mbLeft then begin
    SelEndY:=min(curscrollpos+(y div Fcharsize.y),flines.count-1);
    SelEndX:=min(x div Fcharsize.x+1,length(flines[selendy]));
    BusySelecting:=false;
    PopupMenu.Items[0].Enabled:=true;
    PopupMenu.Items[1].Enabled:=true;
    ClipCursor(nil);
    ReleaseCapture;
    if selendy > lastsely then begin
      temprect.top:=min(max(0,lastsely-curscrollpos),fclientsize.y-1)*fcharsize.y;
      temprect.bottom:=max(0,min(selendy+1-curscrollpos,fclientsize.y))*fcharsize.y;
    end
    else begin
      temprect.top:=min(max(0,selendy-curscrollpos),fclientsize.y-1)*fcharsize.y;
      temprect.bottom:=max(0,min(lastsely+1-curscrollpos,fclientsize.y))*fcharsize.y;
    end;
    temprect.left:=0;
    temprect.right:=width;
    invalidaterect(handle,@temprect,true);
    if (SelEndX = Selstartx) and (selendy = selstarty) then begin
      if enablelinks then begin
        if (comptype <> 'sb') and (PTAttrib(flines.objects[selendy])[selendx].linkindex <> 0) and
          (assigned(fonlinkclick)) then
          fonlinkclick(fhyperlinks[PTAttrib(flines.objects[selendy])[selendx].linkindex]);
        if comptype = 'sb' then begin
          if ((PTAttrib(flines.objects[selendy])[selendx].linkindex<>0) and
            assigned(tmudansisb(self).fdisplay) and
            assigned(tmudansisb(self).fdisplay.onlinkclick)) then
            tmudansisb(self).fdisplay.onlinkclick(tmudansisb(self).fdisplay.fhyperlinks[PTAttrib(flines.objects[selendy])[selendx].linkindex]);
        end;
      end;
      SelEndx:=-1;
      SelStartx:=-1;
      SelEndy:=-1;
      SelStarty:=-1;
      lastselx:=-1;
      lastsely:=-1;
      PopupMenu.Items[0].Enabled:=false;
      PopupMenu.Items[1].Enabled:=false;
    end
    else begin
      if autocopy then CopySelectedText(nil);
    end;
    update;
    if assigned(fmysocket) and (processing=0) then fMySocket.resume;
  end;
end;

function TMUDAnsi.SelectedText: string;
var
  i: integer;
  buildstring, efficientstring: string;
begin
  if (SelEndY = -1) or (SelEndX = -1) or (flines.count = 0) or busyselecting or
     ((flines.count < selstarty) and (flines.count < selendy)) then result:=''
  else begin
    if selstarty = selendy then begin
      if selstartx > selendx then begin
        i:=selstartx;
        selstartx:=selendx;
        selendx:=i;
      end;
    end
    else if selstarty > selendy then begin
      i:=selstarty;
      selstarty:=selendy;
      selendy:=i;
      i:=selstartx;
      selstartx:=selendx;
      selendx:=i;
    end;
    if selendy > flines.count-1 then selendy:=flines.count-1;
    buildstring:='';
    if selstarty = selendy then begin
      efficientstring:=FLines[selstarty];
      for i:=selstartx to selendx do buildstring:=buildstring + efficientstring[i];
    end
    else begin
      buildstring:=FLines[selstarty];
      delete(buildstring,1,selstartx-1);
      for i:= selstarty+1 to selendy -1 do buildstring:=buildstring+#13+#10+FLines[i];
      buildstring:=buildstring+#13+#10;
      efficientstring:=FLines[selendy];
      delete(efficientstring,selendx+1,length(efficientstring)-selendx);
      buildstring:=buildstring+efficientstring;
    end;
    result:=buildstring;
  end;
end;

procedure TMUDAnsi.UpOnMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
  temprect: trect;
  tempoint: Tpoint;
  tx, ty: integer;
begin
  if sender <> self then exit;
  if not BusySelecting then begin
    ty:=curscrollpos+(y div Fcharsize.y);
    if (ty < flines.count) and enablelinks then begin
      tx:=max(0,x div Fcharsize.x + 1);
      if (tx > 0) and (tx <= length(flines[ty])) and
        (ptattrib(flines.objects[ty])^[tx].linkindex<>0) then cursor:=crhandpoint
      else cursor:=crdefault;
    end
    else cursor:=crdefault;
    if assigned(fOnMouseMove2) then FOnMouseMove2(sender,shift,x,y);
    exit;
  end;
  recalcrange;
  if y<2 then begin
    scrolldown(1);
    getcursorpos(tempoint);
    setcursorpos(tempoint.x,tempoint.y+1);
  end
  else if y > height-3 then begin
    scrollup(1);
    getcursorpos(tempoint);
    setcursorpos(tempoint.x,tempoint.y-1);
  end;
  selendx:=x div fcharsize.x + 1;
  SelEndY:=curscrollpos+(y div Fcharsize.y);
  if (justselecting) and (selendx = selstartx) and (selendy = selstarty) then exit;
  justselecting:=false;
  if selendy > lastsely then begin
    temprect.top:=min(max(0,lastsely-curscrollpos),fclientsize.y-1)*fcharsize.y;
    temprect.bottom:=max(0,min(selendy+1-curscrollpos,fclientsize.y))*fcharsize.y;
  end
  else begin
    temprect.top:=min(max(0,selendy-curscrollpos),fclientsize.y-1)*fcharsize.y;
    temprect.bottom:=max(0,min(lastsely+1-curscrollpos,fclientsize.y))*fcharsize.y;
  end;
  if selendy=lastsely then begin
    if lastselx > selendx then begin
      temprect.left:=fcharsize.x*(max(0,selendx-1));
      temprect.right:=fcharsize.x*(min(fclientsize.x,lastselx)+1);
    end
    else begin
      temprect.left:=fcharsize.x*(max(0,lastselx-1));
      temprect.right:=fcharsize.x*(min(fclientsize.x,selendx)+1);
    end;
  end
  else begin
    temprect.left:=0;
    temprect.right:=width;
  end;
  lastsely:=selendy;
  lastselx:=selendx;
  invalidaterect(handle,@temprect,true);
  updatewindow(handle);
end;

procedure TMUDAnsi.CopySelectedText(Sender: TObject);
var tempstr: string;
begin
  tempstr:=selectedtext;
  clipboard.settextbuf(pchar(tempstr));
end;

procedure TMUDANSI.InsertFormatedText(FG, BG: integer; InsertedText: string; iital, iuline: boolean);
var
  curattr: TTextAttrib;
  i: integer;
begin
  curattr:=fg shl 8;
  curattr:=curattr or (bg shl 12);
  if iital then curattr:=curattr or tsitalic;
  if iuline then curattr:=curattr or tsuline;
  for i:=length(texttoinsert)+1 to length(texttoinsert)+length(insertedtext) do begin
    texttoinsertattribs^[i].textattrib:=curattr;
    texttoinsertattribs^[i].linkindex:=0;
  end;
  texttoinsert:=texttoinsert+insertedtext;
  if processing = 0 then process(' '+#8);
end;

procedure TMUDAnsi.Ansify(var S: string; P: PTTextArray);
var s2 : string;

procedure ProcessEsc;
var
  DeleteNum: integer;
  ts: string[5];
  Num: array[0..20] of word;

procedure GetNum(cx: byte);
var code: integer;
begin
  ts:='';
  while (length(s) > 0) and (s[1] in ['0'..'9']) do begin
    ts:=ts+s[1];
    lastansi:=lastansi+s[1];
    Delete(s,1,1);
  end;
  val(ts,Num[cx],code)
end;

begin {processesc}
  lastansi:=#27;
  if s[2] <> '[' then begin
    if length(s) > 1 then lastansi:='';
    Delete(s,1,2);
    exit;
  end;
  lastansi:=lastansi+'[';
  Delete(s,1,2);
  if length(s) > 0 then fillchar(Num,sizeof(Num),#255);
  GetNum(0);
  DeleteNum:=1;
  while (length(s) > 0) and (s[1] = ';') and (DeleteNum < 21) do begin
    lastansi:=lastansi+s[1];
    Delete(s,1,1);
    GetNum(DeleteNum);
    DeleteNum:=DeleteNum+1;
  end;
  if length(s) > 0 then begin
    lastansi:='';
    case UpCase(s[1]) of
      'M': begin
             DeleteNum := 0;
             while (Num[DeleteNum] <> 65535) or (DeleteNum = 0) do begin
               case Num[DeleteNum] of
{all attributes off}
                 0: CurrentAttributes:=DefaultAttributes;
{bold on}
                 1: CurrentAttributes:=(CurrentAttributes or tsBold) and (not tsDefault);
{bold on}
                 2: CurrentAttributes:=(CurrentAttributes or tsBoldBG) and (not tsDefaultbg);
{Italics on}
                 3: CurrentAttributes:=(CurrentAttributes or tsItalic);
{Underline on}
                 4: CurrentAttributes:=(CurrentAttributes or tsUline);
{blink on}
                 5: CurrentAttributes:=(CurrentAttributes or tsFlash);
{reverse on}
                 7: CurrentAttributes:=(CurrentAttributes or tsInverse);
{invisible on}
                 8: CurrentAttributes:=(CurrentAttributes or tsInvis) and (not tsDefault);
{general FG}
            30..37: CurrentAttributes:=(CurrentAttributes and $F8FF or ((Num[DeleteNum]-30) shl 8)) and (not tsDefault);
{general GB}
            40..47: CurrentAttributes:=(CurrentAttributes and $8FFF or ((Num[DeleteNum]-40) shl 12)) and (not tsDefaultbg);
               end;
               DeleteNum:=DeleteNum+1;
             end;
             DeleteNum:=1;
           end;
    end;
  end;
  Delete(s,1,DeleteNum);
end;

begin
  s:=lastAnsi+s;
  s2:='';
  while length(s) > 0 do begin
    if s[1] = #27 then ProcessEsc
    else begin
      s2:=s2+s[1];
      p^[length(s2)].textattrib:=currentAttributes;
      p^[length(s2)].linkindex:=0;
      delete(s,1,1);
    end;
  end;
  s:=s2;
end;

procedure TMUDAnsi.Showline(adc:hdc;lineno,location,offset,finish:integer);
var
  i, x, y, fn, lstr: integer;
  ws, str: string;
  attribs: PTAttrib;
  attrib: TTextAttrib;
  flashcolor, bgcol, fgcol: tcolor;
  holdf: hfont;
  myfont: tfont;
  lno: word;
label badgoto;

function trueattrib:TTextattrib;
begin
  if isselected(i,lineno) then trueattrib:=(attribs[i].textattrib or tsselected)
  else trueattrib:=attribs[i].textattrib;
end;

begin
  y:=location*fcharsize.y;
  x:=(offset-1)*fcharsize.x;
  drawback(adc,rect(0,y,width,y+fcharsize.y-1));
  ws:=flines[lineno];
  if ws = '' then exit;
  attribs:=PTAttrib(flines.Objects[lineno]);
  fn:=min(min(length(ws),finish),fclientsize.x);
  i:=offset;
  myfont:=tfont.create;
  myfont.assign(ffont);
  while i <= fn do begin
    attrib:=trueattrib;
    lno:=attribs[i].linkindex;
    str:='';
    while (i <= fn) and (not enablelinks or (attribs[i].linkindex = lno)) and (trueattrib = attrib) do begin
      str:=str+ws[i];
      inc(i);
    end;
    myfont.style:=ffont.style;
    if (attrib and tsdefault) <> 0 then fgcol:=ftextcolor
    else fgcol:=ColorArray[(attrib and $0F00) shr 8];
    if (attrib and tsDefaultbg) <> 0 then bgcol:=fcolor
    else bgcol:=ColorArray[(attrib and $F000) shr 12];
    if (attrib and tsSelected) <> 0 then begin
      bgcol:=fSelBGColor;
      fgcol:=fselFGColor;
    end;
    if (attrib and tsInverse) <> 0 then begin
      flashcolor:=bgcol;
      bgcol:=fgcol;
      fgcol:=flashcolor;
    end;
    if (attrib and tsItalic) <> 0 then myfont.style:=myfont.style +[fsitalic];
    if (attrib and tsUline)<>0 then myfont.style:=myfont.style +[fsunderline];
    if (lno<>0) and enablelinks then begin
      fgcol:=linkfg;
      bgcol:=linkbg;
      myfont.style:=myfont.style +[fsunderline];
    end;
    if flashon and ((attrib and tsflash) <> 0) then begin
      if fullflash then begin
        flashcolor:=bgcol;
        bgcol:=fgcol;
        fgcol:=flashcolor;
      end
      else fgcol:=bgcol;
    end;
    myfont.color:=fgcol;
    holdf:=selectobject(bdc,myfont.handle);
    setbkcolor(bdc,bgcol);
    settextcolor(bdc,fgcol);
    lstr:=length(str);
    textout(bdc,x,y,pchar(str),lstr);
    selectobject(bdc,holdf);
    x:=x+fcharsize.x*lstr;
  end;
  myfont.destroy;
end;

function TMUDAnsi.GetCurrentAttribs:TTextAttrib;
begin
  if (flines.count = 0) then result:=DefaultAttributes
  else result:=PTAttrib(flines.Objects[flines.count-1])^[length(flines[flines.count-1])].textattrib;
end;

procedure TMUDAnsi.Scrollup(sv: integer);
var i: integer;
begin
  if sv = 0 then exit;
  if sv < 0 then begin
    scrolldown(-1*sv);
    exit;
  end;
  if flines.count < fclientsize.y then exit;
  for i:=1 to sv do begin
    if curscrollpos = (flines.count-fclientsize.y) then exit;
    inc(curscrollpos);
    scrollby(0,-fcharsize.y);
    updatewindow(handle);
    showline(canvas.handle,curscrollpos+fclientsize.y-1,fclientsize.y-1,1,fclientsize.x);
  end;
end;

procedure TMUDAnsi.Scrolldown(sv: integer);
var i: integer;
begin
  if sv < 0 then begin
    scrollup(-1 *sv);
    exit;
  end;
  if flines.count < fclientsize.y then exit;
  for i:=1 to sv do begin
    if curscrollpos = 0 then exit;
    scrollby(0,fcharsize.y);
    dec(curscrollpos);
    showline(canvas.handle,curscrollpos,0,1,fclientsize.x);
  end;
end;

procedure TMUDAnsi.Scrollto(sv: integer);
var newscrollpos: integer;
begin
  if (flines.count < sv+fclientsize.y) then newscrollpos:=max(0,flines.count-fclientsize.y)
  else if (flines.count < fclientsize.y) or (sv < 0) then newscrollpos:=0
  else newscrollpos:=sv;
  if curscrollpos = newscrollpos then exit;
  curscrollpos:=newscrollpos;
  repaint;
end;

procedure TMUDansi.processtriggers(str: string; ptr: PTAttrib);
var
  workingline2, curtrig, strt: string;
  i, j, fark, diff, lnumvar: integer;
  blaharray: strarray;
  cs, sofl, termt: boolean;

function cspos(what, where: string): integer;
begin
  if cs then result:=pos(lowercase(what),lowercase(where))
  else result:=pos(what,where);
end;

function searchstr(target, strin: string; depth: integer): strarray;
var
  iscan, numvars, cptr, pholder, endspaces: integer;
  getarray, workingarray: strarray;
  strptr, nstrptr, chunk, antichunk, holdch: string;
begin
  numvars:=0;
  for iscan:=0 to 27 do workingarray[iscan]:='';
  if (strin = '') and (target <> '') then begin
    workingarray[0]:='';
    result:=workingarray;
    exit;
  end;
  for iscan:=1 to length(target) do begin
    if target[iscan] = '*' then inc(numvars);
  end;
  if numvars = 0 then begin
    if target = '' then begin
      workingarray[depth]:=strin;
      workingarray[0]:='OK';
    end
    else begin
      pholder:=cspos(target,strin);
      if pholder <> 0 then begin
        if(sofl and (depth = 0) and (pholder <> 1)) then begin
          workingarray[0]:='';
          result:=workingarray;
          exit;
        end;
        strptr:=strin;
        delete(strptr,pholder,length(strptr));
        workingarray[depth]:=strptr;
        workingarray[0]:='OK';
      end
      else workingarray[0]:='';
    end;
    result:=workingarray;
    exit;
  end
  else begin
    strptr:=strin;
    chunk:=target;
    antichunk:=target;
    cptr:=cspos('*',chunk);
    if cptr = 0 then begin
      showmessage('Screw-up in search');
      result:=workingarray;
      exit;
    end;
    delete(chunk,cptr,length(chunk));
    delete(antichunk,1,cptr);
    while(length(strptr) > 0) do begin
      if (chunk <> '') then begin
        pholder:=cspos(chunk,strptr);
        if pholder <> 0 then begin
          if(sofl and (depth = 0) and (pholder <> 1)) then begin
            workingarray[0]:='';
            result:=workingarray;
            exit;
          end;
          nstrptr:=strptr;
          delete(nstrptr,1,pholder+length(chunk)-1);
          if nstrptr = '' then begin
            workingarray[0]:='';
            result:=workingarray;
            exit;
          end;
          holdch:=nstrptr[1];
          delete(nstrptr,1,1);
          getarray:=searchstr(antichunk,nstrptr,depth+1);
          if getarray[0] = 'OK' then begin
            workingarray:=getarray;
            workingarray[depth+1]:=holdch+getarray[depth+1];
            nstrptr:=strptr;
            delete(nstrptr,cspos(chunk,strptr),length(nstrptr));
            workingarray[depth]:=nstrptr;
            workingarray[0]:='OK';
            result:=workingarray;
            exit;
          end
          else begin
            workingarray[0]:='';
            result:=workingarray;
            exit;
          end;
        end
        else begin
          workingarray[0]:='';
          result:=workingarray;
          exit;
        end;
      end
      else begin {chunk=''}
        nstrptr:=strptr;
        holdch:=nstrptr[1];
        delete(nstrptr,1,1);
        getarray:=searchstr(antichunk,nstrptr,depth+1);
        if getarray[0] = 'OK' then begin
          workingarray:=getarray;
          workingarray[depth+1]:=holdch+getarray[depth+1];
          if depth = 0 then begin
            endspaces:=0;
            while (workingarray[1])[length(workingarray[1])] = ' ' do begin
              delete(workingarray[1],length(workingarray[1]),1);
              inc(endspaces);
            end;
            while (pos(' ',workingarray[1]) <> 0) do delete(workingarray[1],1,pos(' ',workingarray[1]));
            while endspaces > 0 do begin
              workingarray[1]:=workingarray[1]+' ';
              dec(endspaces);
            end;
          end;
          result:=workingarray;
          exit;
        end
        else begin
          workingarray[0]:='';
          result:=workingarray;
          exit;
        end;
      end;
    end;
    workingarray[0]:='';
    result:=workingarray;
    exit;
  end;
end;

begin
  j:=0;
  termt:=false;
  if str = '' then exit;
  if not inserting then begin
    while ((j < MAXTRIGGERS) and (Triggers[j] <> '') and (not termt)) do begin
      curtrig:=triggers[j];
      strt:=str;
      cs:=triggercaseinsensitive[j];
      sofl:='^' = curtrig[1];
      if sofl then delete(curtrig,1,1);
      blaharray:=searchstr(curtrig,strt,0);
      if blaharray[0] = 'OK' then begin
        lnumvar:=0;
        workingline2:='';
        for i:=1 to length(curtrig) do begin
          if curtrig[i] = '*' then begin
            inc(lnumvar);
            workingline2:=workingline2+blaharray[lnumvar];
          end
          else workingline2:=workingline2+curtrig[i];
        end;
        fark:=cspos(workingline2,strt);
        if fark = 0 then Showmessage('Trigger expansion farked up');
        diff:=length(workingline2)+fark-1;
        if ((triggercolor[j] <> 0) or (triggerbgcolor[j] <> triggercolor[j])) then begin
          for i:= fark to diff do begin
            ptr^[i].textattrib:=((ptr^[i].textattrib and $007E)or(triggercolor[j] shl 8) or
              (triggerbgcolor[j] shl 12));
          end;
        end;
        if assigned(fontrigger) then begin
          filterline:=TriggerFilter[j];
          OnTrigger(j,blaharray);
        end;
        if triggerterminal[j] then termt:=true;
      end;
      inc(j);
    end;
  end;
end;

function TMUDansi.processfilters(str: string): boolean;
var i: integer;
begin
  i:=0;
  if not inserting then begin
    while (i < MAXFILTERS) and (Filters[i] <> '') do begin
      if FiltersCaseInsensitive[i] then str:=lowercase(str);
      if strpos(pchar(str),pchar(Filters[i])) <> nil then begin
        result:=true;
        if FiltersRetain[i] then begin
          if assigned(FOnFilter) then FOnFilter(str);
        end;
        exit;
      end;
      inc(i);
    end;
  end;
  result:=false;
end;

procedure TMUDAnsi.WMSize(var M: TWMSize);
begin
  RecalcRange;
  curscrollpos:=max(0,flines.count-fclientsize.y);
  if bgcreated then
  begin
    deletedc(bdc);
    deleteobject(bbmp);
    bdc:=createcompatibledc(canvas.handle);
    bbmp:=createcompatiblebitmap(canvas.handle,width,height);
    selectobject(bdc,bbmp);
  end;
  inherited;
  update;
end;

function TMUDAnsi.SelectedRect: TRect;
var workrect: trect;
begin
  If ((selstartx = -1) or (selendx = -1) or (selstarty = -1) or (selendy = -1)) then
    workrect:=rect(-1,-1,-1,-1)
  else if (selstarty = selendy) then begin
    if (selstartx < selendx) then workrect:=rect(selstartx,selstarty,selendx,selendy)
    else workrect:=rect(selendx,selstarty,selstartx,selendy);
  end
  else if (selstarty < selendy) then workrect:=rect(selstartx,selstarty,selendx,selendy)
  else workrect:=rect(selendx,selendy,selstartx,selstarty);
  result:=workrect;
end;

function TMUDAnsi.IsSelected(x, y: integer): boolean;
var workrect: TRect;
begin
  workrect:=selectedrect;
  if workrect.left = -1 then result:=false
  else if (y = workrect.top) and (y = workrect.bottom) and (x >= workrect.left) and
    (x <= workrect.right) then result:=true
  else if (y = workrect.top) and (x >= workrect.Left) and (y <> workrect.bottom) then result:=true
  else if (y > workrect.top) and (y < workrect.bottom) then result:=true
  else if (y = workrect.bottom) and (x <= workrect.right) and (y <> workrect.top) then result:=true
  else result:=false;
end;

function TMUDAnsi.IsVis(lineno: integer): boolean;
begin
  result:=((lineno >= curscrollpos) and (lineno < curscrollpos + fclientsize.y));
end;

procedure TMUDAnsi.CopyToAutotyper(Sender:TObject);
begin
  if assigned(fOnCopyToAutoTyper) then OnCopyToAutoTyper(selectedtext);
end;

procedure TMUDAnsi.ClearTriggers;
var i: integer;
begin
  for i:=0 to maxtriggers-1 do begin
    triggers[i]:='';
    triggercolor[i]:=ANSI_DFG;
    triggerbgcolor[i]:=ANSI_DBG;
  end;
end;

procedure TMUDAnsi.clearfilters;
var i: integer;
begin
  for i:=0 to MAXFILTERS-1 do Filters[i]:='';
end;

procedure TMUDAnsi.ProcessInserted(var j: integer);
var
  i: integer;
  ach: char;
  tempstr: string;
begin
  if texttoinsert = '' then exit;
  texttoinsertattribs[0].textattrib:=currentattributes;
  i:=1;
  while i <= length(texttoinsert) do begin
    ach:=texttoinsert[i];
    if ((j > maxstrlen) and (ach <> #13)) or (ach = #10) then begin
      if (ach <> #10) then dec(i);
      add('');
      if (not busyselecting) then begin
        if isvis(flines.count-2) then scrollup2(1)
        else scrollto(flines.count-2);
      end;
      if assigned(fOnLineCompleted) then fOnLineCompleted(flines[flines.count-2],
        flines.objects[flines.count-2]);
      j:=1;
    end
    else if ((ach <= #255) and (ach > #31)) then begin
      flines[flines.count-1]:=flines[flines.count-1]+ach;
      PTAttrib(flines.objects[flines.count-1])^[j]:=texttoinsertattribs^[i];
      inc(j);
    end
    else if ach = #8 then begin
      dec(j);
      if j<1 then j:=1;
      tempstr:=flines[flines.count-1];
      delete(tempstr,j,1);
      flines[flines.count-1]:=tempstr;
    end;
    inc(i);
  end;
  texttoinsert:='';
  currentattributes:=texttoinsertattribs[0].textattrib;
end;

procedure TMUDAnsi.clear;
var i: integer;
begin
  for i:=0 to flines.count-1 do dispose(PTAttrib(Flines.objects[i]));
  flines.clear;
  curscrollpos:=0;
  currentattributes:=defaultattributes;
  repaint;
end;

procedure TMUDAnsi.SetAutocopy(b: boolean);
begin
  fautocopy:=b;
  if fscrollback <> nil then fscrollback.autocopy:=b;
end;

procedure TMUDAnsi.SetOnCopyToAutotyper(ate: TMUDCopyToATEvent);
begin
  fOnCopyToAutoTyper:=ate;
  if fScrollback <> nil then fscrollback.OnCopyToAutoTyper:=ate;
end;

procedure TMUDAnsi.SetSB(aScroll: TMUDAnsiSB);
var tempSB:TMUDAnsiSB;
begin
  if AScroll = fScrollBack then exit;
  if fscrollback <> nil then begin
    tempSB:=fscrollback;
    fscrollback:=nil;
    tempSB.Display:=nil;
  end;
  fscrollback:=ascroll;
  if fscrollback <> nil then fscrollback.Display:=self;
end;

procedure TMUDAnsi.CallOnOptions(Sender: TObject);
begin
  if Assigned(FOnOptions) then FOnOptions(self);
end;

procedure TMUDAnsi.Unselect;
begin
  if (busyselecting) then exit;
  SelEndx:=-1;
  SelStartx:=-1;
  SelEndy:=-1;
  SelStarty:=-1;
  lastselx:=-1;
  lastsely:=-1;
  PopupMenu.Items[0].Enabled:=false;
  PopupMenu.Items[1].Enabled:=false;
  repaint;
end;

procedure TMUDAnsi.setInc(value: integer);
var i: integer;
begin
  i:=value mod 100000;
  if (i < 0) then exit;
  finc:=i;
  fincstr:=format('%.5d',[finc]);
end;

procedure TMUDANSI.setcolors(i,val:integer);
begin
  colorarray[i]:=val;
  repaint;
end;

function TMUDANSI.getcolors(i: integer): integer;
begin
  getcolors:=colorarray[i];
end;

constructor TMUDAnsiSB.Create(AnOwner: TComponent);
begin
  inherited create(AnOwner);
  comptype:='sb';
  fdisplay:=nil;
end;

procedure TMUDAnsiSB.setDisplay(aDisplay: TMUDAnsi);
var
  tempdisplay: TMUDAnsi;
  i: integer;
begin
  if adisplay = fdisplay then exit;
  if fdisplay <> nil then begin
    tempdisplay:=fdisplay;
    fdisplay:=nil;
    tempdisplay.Scrollback:=nil;
  end;
  Clear;
  fdisplay:=adisplay;
  if fdisplay <> nil then begin
    fdisplay.Scrollback:=self;
    for i:=0 to fdisplay.flines.count-1 do Addline(fdisplay.flines[i],PTAttrib(fdisplay.flines.objects[i]));
    fautocopy:=fdisplay.autocopy;
    foncopytoautotyper:=fdisplay.oncopytoautotyper;
  end;
  repaint;
end;

procedure TMUDAnsiSB.AddLine(str: string; ptr: PTAttrib);
var p: PTAttrib;
begin
  add(str);
  p:=PTAttrib(flines.objects[flines.count-1]);
  move(ptr^,p^,sizeof(TAttrib));
  if isvis(flines.count-1) then invalidate;
end;

procedure TMUDAnsi.Scrollup2(sv: integer);
var i: integer;
begin
  if sv = 0 then exit;
  if sv < 0 then begin
    scrolldown2(-1 *sv);
    exit;
  end;
  if flines.count < fclientsize.y then exit;
  for i:=1 to sv do begin
    if curscrollpos = (flines.count - fclientsize.y) then exit;
    inc(curscrollpos);
  end;
end;

procedure TMUDAnsi.Scrolldown2(sv: integer);
var i: integer;
begin
  if sv < 0 then begin
    scrollup2(-1 *sv);
    exit;
  end;
  if flines.count < fclientsize.y then exit;
  for i:=1 to sv do begin
    if curscrollpos = 0 then exit;
    dec(curscrollpos);
  end;
end;

procedure tmudansi.flash;
begin
  flashon:=(not flashon);
  invalidate;
end;

procedure tmudansi.setselfgcolor(col: tcolor);
begin
  fselfgcolor:=col;
  invalidate;
end;

procedure tmudansi.setselbgcolor(col: tcolor);
begin
  fselbgcolor:=col;
  invalidate;
end;

procedure tmudansi.setlinkfg(col: tcolor);
begin
  flinkfg:=col;
  invalidate;
end;

procedure tmudansi.setlinkbg(col: tcolor);
begin
  flinkbg:=col;
  invalidate;
end;

procedure TMUDANSI.InsertLink(text, target: string);
var
  curattr: TTextAttrib;
  i: integer;
begin
  curattr:=getcurrentattribs;
  for i:=length(texttoinsert)+1 to length(texttoinsert)+length(text) do begin
    texttoinsertattribs^[i].textattrib:=curattr;
    texttoinsertattribs^[i].linkindex:=currentlink;
  end;
  fHyperLinks[currentlink]:=target;
  inc(currentlink);
  if currentlink = 0 then currentlink:=1;
  texttoinsert:=texttoinsert + text;
  if processing = 0 then process(' '+#8);
end;

function tmudansi.GetLink(idx:integer): string;
begin
  result:=fhyperlinks[idx];
end;

function tmudansi.find(str: string): integer;
var
  i, p: integer;
  s: string;
begin
  if selectedtext <> '' then begin
    if busyselecting then begin
      find:=-1;
      exit;
    end;
    if selstarty = selendy then begin
      s:=flines[selstarty];
      delete(s,selendx+1,length(s)-selendx);
      delete(s,1,selstartx-1);
      p:=pos(str,s);
      if p <> 0 then begin
        inc(selstartx,p-1);
        selendx:=selstartx+length(str)-1;
        find:=1;
        if not isvis(selstarty) then scrollto(selstarty)
        else invalidate;
        exit;
      end
      else begin
        find:=0;
        exit;
      end
    end
    else begin
      i:=selstarty;
      s:=flines[i];
      delete(s,1,selstartx-1);
      p:=pos(str,s);
      if p <> 0 then begin
        inc(selstartx,p-1);
        selendx:=selstartx+length(str)-1;
        selendy:=selstarty;
        find:=1;
        if not isvis(selstarty) then scrollto(selstarty)
        else invalidate;
        exit;
      end;
      inc(i);
      while i < selendy do begin
        s:=flines[i];
        p:=pos(str,s);
        if p <> 0 then begin
          selstartx:=p;
          selendx:=p+length(str)-1;
          selstarty:=i;
          selendy:=i;
          find:=1;
          if not isvis(selstarty) then scrollto(selstarty)
          else invalidate;
          exit;
        end;
        inc(i);
      end;
      s:=flines[i];
      delete(s,selendx+1,length(s)-selendx);
      p:=pos(str,s);
      if p <> 0 then begin
        selstartx:=p;
        selendx:=p+length(str)-1;
        selstarty:=i;
        selendy:=i;
        find:=1;
        if not isvis(selstarty) then scrollto(selstarty)
        else invalidate;
        exit;
      end;
      find:=0;
      exit;
    end;
  end
  else begin
    for i:=0 to flines.count - 1 do begin
      s:=flines[i];
      p:=pos(str,s);
      if p <> 0 then begin
        selstartx:=p;
        selendx:=p+length(str)-1;
        selstarty:=i;
        selendy:=i;
        find:=1;
        if not isvis(selstarty) then scrollto(selstarty)
        else invalidate;
        exit;
      end;
    end;
    find:=0;
    exit;
  end;
end;

function tmudansi.findnext(str: string): integer;
var
  i, p: integer;
  s: string;
begin
  if (selectedtext = '') then findnext:=find(str)
  else begin
    if busyselecting then begin
      findnext:=-1;
      exit;
    end;
    if selectedtext = str then begin
      i:=selendy;
      s:=flines[i];
      delete(s,1,selendx);
      p:=pos(str,s);
      if p <> 0 then begin
        selstarty:=selendy;
        selstartx:=selendx+p+1;
        inc(selendx,p);
        findnext:=1;
        if not isvis(selstarty) then scrollto(selstarty)
        else invalidate;
        exit;
      end;
      for i:=selendy+1 to flines.count-1 do begin
        s:=flines[i];
        p:=pos(str,s);
        if p <> 0 then begin
          selstartx:=p;
          selendx:=p+length(str)-1;
          selstarty:=i;
          selendy:=i;
          findnext:=1;
          if not isvis(selstarty) then scrollto(selstarty)
          else invalidate;
          exit;
        end;
      end;
      findnext:=0;
    end
    else begin
      for i:=0 to flines.count-1 do begin
        s:=flines[i];
        p:=pos(str,s);
        if p <> 0 then begin
          selstartx:=p;
          selendx:=p+length(str)-1;
          selstarty:=i;
          selendy:=i;
          findnext:=1;
          if not isvis(selstarty) then scrollto(selstarty)
          else invalidate;
          exit;
        end;
      end;
      findnext:=0;
    end;
  end;
end;

procedure TMUDAnsi.UponDeactivate(Sender: TObject);
var cursorxy: TPoint;
begin
  if (busyselecting) then begin
    getcursorpos(cursorxy);
    OnMouseUp(self,mbleft,[],Cursorxy.x-clientorigin.x,cursorxy.y-clientorigin.y);
  end;
end;

procedure TMUDAnsi.processurls(str: string; ptr: PTAttrib);
var
  i, j, start, finish: integer;
  workstr, lcstr:string;
  notalink: boolean;
begin
  lcstr:=lowercase(str);
  for i:=1 to nURLs do begin
    workstr:=urlstrings[i];
    start:=pos(workstr,str);
    if start <> 0 then begin
      finish:=start+length(workstr)-1;
      while ((finish < length(lcstr)) and (not (lcstr[finish+1] in [' ']))) do inc(finish);
      while (lcstr[finish]='.') do dec(finish);
      while (lcstr[finish]='"') do dec(finish);
      while (lcstr[finish]='''') do dec(finish);
      while (lcstr[finish]=')') do dec(finish);
      if ((finish-start+1) > length(workstr)) then begin
        notalink:=true;
        j:=start;
        while (j <= finish) and (notalink) do begin
          if ptr^[j].linkindex <> 0 then notalink:=false;
          inc(j);
        end;
        if notalink then begin
          workstr:='';
          for j:=start to finish do begin
            workstr:=workstr+str[j];
            ptr^[j].linkindex:=currentlink;
          end;
          fhyperlinks[currentlink]:=workstr;
          inc(currentlink);
          if(currentlink=0) then currentlink:=1;
        end;
      end;
    end;
  end;
end;

procedure TMUDAnsi.SetEnableLinks(ena: boolean);
begin
  if ena <> fenablelinks then begin
    fenablelinks:=ena;
    invalidate;
  end
end;

procedure TMUDAnsi.Process(str: string);
var
  workingline, workingline2, tempstr, tempstr2: string;
  ptr, ptr2, ptr3, ptr4: PTTextArray;
  remember:longint;
  i, j, v, k, lc:integer;
  ach: char;
  tempattr: PTAttrib;
begin
  if assigned(fmysocket) then fmysocket.pause;
  filterline:=false;
  inc(processing);
  workingline:=str;
  workingline2:='';
  if flines.count = 0 then add('');
  i:=1;
  if not inserting then begin
    while i <= length(workingline) do begin
      ach:=workingline[i];
      if ScanState = 'NORMAL' then begin
        if (ach = fcodes[1]) then begin
          ScanState:=ach;
          if JSState <> 'NORMAL' then begin
            workingline2:=workingline2+JSState;
            JSState:='NORMAL';
          end
        end
        else begin
          if JSState = 'NORMAL' then begin
            if (ach = qJS[1]) then JSState:=ach
            else workingline2:=workingline2+ach;
          end
          else begin
            JSState:=JSState+ach;
            if pos(jsstate,qjs) <> 1 then begin
              workingline2:=workingline2+jsstate;
              jsstate:='NORMAL';
            end
            else begin
              if jsstate = qjs then begin
                if (fenablejumpstart) and (assigned(fonjumpstart)) then FOnJumpstart(self);
                workingline2:=workingline2+jsstate;
                JSState:='NORMAL';
              end;
            end;
          end;
        end;
      end
      else begin
        if ScanState = fcodes[1] then begin
          if ach <> fcodes[2] then begin
            workingline2:=workingline2+ScanState;
            ScanState:='NORMAL';
            incstring:='';
            dec(i);
          end
          else ScanState:=ScanState + ach;
        end
        else if ScanState = (fcodes[1]+fcodes[2]) then begin
          if ach <> fcodes[3] then begin
            workingline2:=workingline2+ScanState;
            ScanState:='NORMAL';
            incstring:='';
            dec(i);
          end
          else ScanState:=ScanState+ach;
        end
        else if StrLComp(PChar(ScanState),pchar(fcodes),3) <> 0 then begin
          ScanState := 'NORMAL';
          incstring:='';
          dec(i);
        end
        else if length(incstring) <5 then begin
          if not (ach in ['0'..'9']) then begin
            ScanState:='NORMAL';
            incstring:='';
            dec(i);
          end
          else incstring:=incstring+ach;
        end
        else if (Length(ScanState) < 6) then begin
          if not (ach in ['0'..'9']) then begin
            ScanState:='NORMAL';
            incstring:='';
            dec(i);
          end
          else ScanState:=ScanState+ach;
        end
        else if Length(ScanState) < strtoint(scanstate[4]+scanstate[5]+scanstate[6])+6 then begin
          scanstate:=scanstate+ach;
          if length(ScanState)= strtoint(scanstate[4]+scanstate[5]+scanstate[6])+6 then begin
            delete(ScanState,1,6);
            tempstr:=scanstate;
            ScanState:='NORMAL';
            if (strtoint(incstring)<>finc) then begin
              if FShowBadCodes then workingline2:=workingline2+tempstr;
            end
            else begin
              if assigned(foncodereceived) then OnCodeReceived(tempstr);
            end;
            incstring:='';
          end;
        end
      end;
      inc(i);
    end; {while}
  end
  else workingline2:=str;
  workingline:=workingline2;
  remember:=length(workingline)+2000;
  getmem(ptr,remember*sizeof(TTextstruct));
  getmem(ptr2,remember*sizeof(TTextstruct));
  ptr^[0].textattrib:=CurrentAttributes;
  ansify(workingline,ptr);
  i:=1;
  j:=length(flines[flines.count-1])+1;
  while i <= length(workingline) do begin
    processinserted(j);
    ach:=workingline[i];
    if ((j>maxstrlen) and (ach <> #13)) or (ach = #10) then begin
      if (ach <> #10) then dec(i);
      if not processfilters(flines[flines.count-1]) then begin
        if (not busyselecting) then begin
          if isvis(flines.count-1) then begin
            add('');
            lc:=flines.count;
            processtriggers(flines[flines.count-2],PTAttrib(flines.objects[flines.count-2]));
            if lc=flines.count then scrollup2(1)
            else scrollup2(1);
          end
          else begin
            add('');
            processtriggers(flines[flines.count-2],PTAttrib(flines.objects[flines.count-2]));
            scrollto(flines.count-1);
          end;
          if assigned(fOnLineCompleted) then
            fOnLineCompleted(flines[flines.count-2],flines.objects[flines.count-2]);
          if (fScrollBack <> nil) and CopyToScrollback then
            fscrollback.addline(flines[flines.count-2],PTAttrib(flines.objects[flines.count-2]));
        end
        else begin
          add('');
          processtriggers(flines[flines.count-2],PTAttrib(flines.objects[flines.count-2]));
          if assigned(fOnLineCompleted) then
            fOnLineCompleted(flines[flines.count-2],flines.objects[flines.count-2]);
          if (fScrollBack<>nil) and CopyToScrollback then
            fscrollback.addline(flines[flines.count-2],PTAttrib(flines.objects[flines.count-2]));
        end;
        processurls(flines[flines.count-2],PTAttrib(flines.objects[flines.count-2]));
      end
      else begin
        tempstr:=flines[flines.count-1];
        tempattr:=PTAttrib(flines.objects[flines.count-1]);
        flines[flines.count-1]:='';
        if (flines.count >1) and (flines[flines.count-2]='') then begin
          dispose(PTAttrib(flines.objects[flines.count-2]));
          flines.delete(flines.count-2);
          scrolldown2(1);
        end;
        if eventsonfilter then begin
          filtering:=true;
          processtriggers(tempstr,tempattr);
          filtering:=false;
        end;
      end;
      j:=1;
    end
    else if ((ach <= #255) and (ach > #31)) then begin
      flines[flines.count-1]:=flines[flines.count-1]+ach;
      PTAttrib(flines.objects[flines.count-1])^[j]:=ptr^[i];
      inc(j);
    end
    else if ach = #8 then begin
      dec(j);
      if j<1 then j:=1;
      tempstr:=flines[flines.count-1];
      delete(tempstr,j,1);
      flines[flines.count-1]:=tempstr;
    end
    else if ach = #7 then begin
      if Assigned(fOnBeep) then fOnBeep(self);
    end
    else if ach= #9 then begin
      tempstr:=workingline;
      move(ptr^,ptr2^,remember*sizeof(ttextstruct));
      delete(workingline,i,length(tempstr)-i+1);
      delete(tempstr,1,i);
      v:=min(maxstrlen-length(flines[flines.count-1]),
        ftabsize-(length(flines[flines.count-1]) mod ftabsize));
      while v > 0 do begin
        workingline:=workingline+' ';
        ptr^[i+v-1]:=ptr^[i];
        dec(v);
      end;
      v:=min(maxstrlen-length(flines[flines.count-1]),
        ftabsize-(length(flines[flines.count-1]) mod ftabsize));
      ptr3:=@(ptr2^[i]);
      ptr4:=@(ptr^[i+v-1]);
      move(ptr3^,ptr4^,(remember-i-v)*sizeof(ttextstruct));
      workingline:=workingline+tempstr;
      dec(i);
    end;
    inc(i);
  end;
  freemem(ptr2,remember*sizeof(TTextstruct));
  freemem(ptr,remember*sizeof(TTextstruct));
  dec(processing);
  if assigned(fmysocket) and (not busyselecting) then fmysocket.resume;
  invalidate;
end;

end.
